/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;
using System.IO;
using System.Configuration.Install;

namespace DaveSexton.DocProject.InstallActions
{
	[Serializable]
	internal sealed class VisualStudioContent
	{
		#region Public Properties
		public string AddinFullPath
		{
			get
			{
				if (allUsers)
					return Path.Combine(addinPath, 
						System.IO.Path.GetFileNameWithoutExtension(addinName) + year +
                        System.IO.Path.GetExtension(addinName));
				else
					return Path.Combine(addinPath, addinName);
			}
		}

		public string Addin { get { return addinName; } }

		public bool IsValid { get { return valid; } }

		public bool HasAddin { get { return addinName != null; } }

		public IList<Template> Templates
		{
			get
			{
				return templates;
			}
		}

		public string VisualStudioInstallPath
		{
			get
			{
				return vsInstallPath;
			}
		}

		public string VisualStudioYear
		{
			get
			{
				return year;
			}
		}

		public string VisualStudioProgramName
		{
			get
			{
				return vsProgramName;
			}
		}

		public string VisualStudioRegistryKey
		{
			get
			{
				return vsKey;
			}
		}

		public string VisualStudioVersionRegistryKey
		{
			get
			{
				return version;
			}
		}
		#endregion

		#region Private / Protected
		private const string rootKey = @"SOFTWARE\Microsoft\";

		private static IList<string> TemplateFolderAllUsersPrecedence
		{
			get
			{
				return new List<string>(new string[] {
					// list folders by precedence in top-down order

					// this folder MUST be at index zero because it's expected by the GetAddinsPathForAllUsers method
					Environment.ExpandEnvironmentVariables(@"%ALLUSERSPROFILE%\Application Data\Microsoft\MSEnvShared\Addins")
				});
			}
		}

		private IList<string> TemplateFolderUserPrecedence
		{
			get
			{
				return new List<string>(new string[] {
					// list folders by precedence in top-down order

					ExpandVisualStudioVariables(@"%VSMYDOCUMENTS%\Addins")
				});
			}
		}

		private readonly string personalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);

		private readonly string addinName, vsKey, version, year, vsProgramName;
		private readonly IList<Template> templates;
		private string templatesPath, addinPath, vsInstallPath, vsInstanceName;
		private bool allUsers, valid, initialized;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ContentPaths" /> class.
		/// </summary>
		public VisualStudioContent(IList<Template> templates, string addinName, string vsProgramName, string vsKey, string version, string year)
		{
			if (templates == null)
				throw new ArgumentNullException("templates");

			if (addinName == null)
				throw new ArgumentNullException("addinName");

			if (vsProgramName == null)
				throw new ArgumentNullException("vsProgramName");

			if (vsKey == null)
				throw new ArgumentNullException("vsKey");

			if (version == null)
				throw new ArgumentNullException("version");

			this.templates = templates;
			this.addinName = addinName;
			this.vsProgramName = vsProgramName;
			this.vsKey = vsKey;
			this.version = version;
			this.year = year;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="ContentPaths" /> class.
		/// </summary>
		public VisualStudioContent(IList<Template> templates, string vsProgramName, string vsKey, string version, string year)
		{
			if (templates == null)
				throw new ArgumentNullException("templates");

			if (vsProgramName == null)
				throw new ArgumentNullException("vsProgramName");

			if (vsKey == null)
				throw new ArgumentNullException("vsKey");

			if (version == null)
				throw new ArgumentNullException("version");

			this.templates = templates;
			this.vsProgramName = vsProgramName;
			this.vsKey = vsKey;
			this.version = version;
			this.year = year;
		}
		#endregion

		#region Methods
		private string GetVSInstanceName(RegistryKey vsRootMachineKey)
		{
			using (RegistryKey vsHelpKey = vsRootMachineKey.OpenSubKey("Help"))
			{
				return (vsHelpKey != null)
					? vsHelpKey.GetValue("UserFilesFolderRoot") as string ?? ("Visual Studio " + year)
					: "Visual Studio " + year;
			}
		}

		public string GetTemplatePath(Template template)
		{
			if (template == null)
				throw new ArgumentNullException("template");

			return Path.Combine(GetTemplateCategoryPath(template), template.Name);
		}

		public string GetTemplateCategoryPath(Template template)
		{
			if (template == null)
				throw new ArgumentNullException("template");

			string category = (string.IsNullOrEmpty(template.Category))
				? string.Empty : Path.DirectorySeparatorChar + template.Category;

			switch (template.Type)
			{
				case TemplateType.VisualCSharp:
					return Path.Combine(templatesPath, (allUsers) ? "CSharp" : "Visual C#") + category;
				case TemplateType.VisualJSharp:
					return Path.Combine(templatesPath, (allUsers) ? "JSharp" : "Visual J#") + category;
				case TemplateType.VisualBasic:
					return Path.Combine(templatesPath, (allUsers) ? "VisualBasic" : "Visual Basic") + category;
				default:
					throw new System.ComponentModel.InvalidEnumArgumentException("template", (int) template.Type, typeof(TemplateType));
			}
		}

		private string ExpandVisualStudioVariables(string path)
		{
			return path.Replace("%VSMYDOCUMENTS%", Path.Combine(personalFolder, vsInstanceName));
		}

		private string GetVisualStudioFolderFromKey(RegistryKey key, string name)
		{
			string path = key.GetValue(name, null, RegistryValueOptions.DoNotExpandEnvironmentNames) as string;

			if (string.IsNullOrEmpty(path))
				return null;

			path = Environment.ExpandEnvironmentVariables(path);

			if (string.IsNullOrEmpty(path) || !Path.IsPathRooted(path))		// only local file and UNC is supported
				return null;
			else
				return ExpandVisualStudioVariables(path);
		}

		private IEnumerable<string> GetVisualStudioFolderList(RegistryKey key)
		{
			foreach (string name in key.GetValueNames())
			{
				if (string.IsNullOrEmpty(name))
					continue;

				// the key is the folder and its value is ignored
				string path = Environment.ExpandEnvironmentVariables(name);

				if (string.IsNullOrEmpty(path))
					continue;

				path = ExpandVisualStudioVariables(path);

				if (string.IsNullOrEmpty(path) || !Path.IsPathRooted(path))		// only local and UNC paths are supported
					continue;

				if (path != null)
					yield return path;
			}
		}

		public void Initialize(bool setAllUsers)
		{
			if (initialized && allUsers == setAllUsers)
				return;

			allUsers = setAllUsers;

			string key = rootKey + vsKey;

			using (RegistryKey vsRootMachineKey = Registry.LocalMachine.OpenSubKey(Path.Combine(key, version)))
			{
				if (vsRootMachineKey != null)
				{
					vsInstallPath = vsRootMachineKey.GetValue("InstallDir", null) as string;

					if (!string.IsNullOrEmpty(vsInstallPath) && Directory.Exists(vsInstallPath))
					{
						vsInstanceName = GetVSInstanceName(vsRootMachineKey);

						if (allUsers)
							InitializeForAllUsers(vsRootMachineKey);
						else
						{
							using (RegistryKey vsRootUserKey = Registry.CurrentUser.OpenSubKey(Path.Combine(key, version)))
							{
								InitializeForCurrentUser(vsRootMachineKey, vsRootUserKey);
							}
						}
					}
				}
			}

			initialized = true;
		}

		private void InitializeForAllUsers(RegistryKey vsRootMachineKey)
		{
			templatesPath = GetTemplatesPathForAllUsers();

			if (string.IsNullOrEmpty(templatesPath))
				return;
			else if (!Directory.Exists(templatesPath))
				Directory.CreateDirectory(templatesPath);

			if (HasAddin)
			{
				addinPath = GetAddinsPathForAllUsers(vsRootMachineKey);

				if (string.IsNullOrEmpty(addinPath))
					return;
				else if (!Directory.Exists(addinPath))
					Directory.CreateDirectory(addinPath);
			}

			valid = true;
		}

		private void InitializeForCurrentUser(RegistryKey vsRootMachineKey, RegistryKey vsRootUserKey)
		{
			templatesPath = GetTemplatesPathForCurrentUser(vsRootUserKey);

			if (string.IsNullOrEmpty(templatesPath))
				return;
			else if (!Directory.Exists(templatesPath))
				Directory.CreateDirectory(templatesPath);

			if (HasAddin)
			{
				addinPath = GetAddinsPathForCurrentUser(vsRootMachineKey, vsRootUserKey);

				if (string.IsNullOrEmpty(addinPath))
					return;
				else if (!Directory.Exists(addinPath))
					Directory.CreateDirectory(addinPath);
			}

			valid = true;
		}

		private string GetTemplatesPathForAllUsers()
		{
			return Path.Combine(vsInstallPath, (HasAddin) ? @"ProjectTemplates" : vsKey + @"\ProjectTemplates");
		}

		private string GetTemplatesPathForCurrentUser(RegistryKey vsRootUserKey)
		{
			string path = GetVisualStudioFolderFromKey(vsRootUserKey, "UserProjectTemplatesLocation");

			if (path == null)
				path = Path.Combine(personalFolder, vsInstanceName + @"\Templates\ProjectTemplates");

			return path;
		}

		private string GetAddinsPathForAllUsers(RegistryKey vsRootMachineKey)
		{
			IList<string> folderPrecedence = TemplateFolderAllUsersPrecedence;

			string folder = GetFolderWithPrecedence(vsRootMachineKey, null, folderPrecedence);

			if ((folder == null || folder.Equals(folderPrecedence[0], StringComparison.OrdinalIgnoreCase))
				&& Environment.OSVersion.Version.Major >= 6)
				// The VS 2005 update for Vista seems to have included a hard-coded path that doesn't appear in the registry
				return Environment.ExpandEnvironmentVariables(@"%ALLUSERSPROFILE%\Microsoft\MSEnvShared\Addins");
			else if (folder == null)
				return folderPrecedence[0];
			else
				return folder;
		}

		private string GetAddinsPathForCurrentUser(RegistryKey vsRootMachineKey, RegistryKey vsRootUserKey)
		{
			string folder = GetFolderWithPrecedence(vsRootMachineKey, vsRootUserKey, TemplateFolderUserPrecedence);

			if (folder == null)
				return TemplateFolderUserPrecedence[0];
			else
				return folder;
		}

		private string GetFolderWithPrecedence(RegistryKey vsRootMachineKey, RegistryKey vsRootUserKey, IList<string> precedenceList)
		{
			string bestFolder = null;
			int bestFolderIndex = precedenceList.Count;

			foreach (string folder in GetSearchFoldersForAddins(vsRootMachineKey, vsRootUserKey))
			{
				if (bestFolder == null)
					bestFolder = folder;

				for (int i = bestFolderIndex - 1; i >= 0; i--)
				{
					string matchFolder = precedenceList[i];

					if (matchFolder != null && folder.Equals(matchFolder.Trim(), StringComparison.OrdinalIgnoreCase))
					{
						if (i == 0)
							return matchFolder;

						bestFolder = matchFolder;
						bestFolderIndex = i;
						break;
					}
				}
			}

			return bestFolder;
		}

		private IEnumerable<string> GetSearchFoldersForAddins(RegistryKey vsRootMachineKey, RegistryKey vsRootUserKey)
		{
			string foldersKey = @"AutomationOptions\LookInFolders";

			if (vsRootUserKey != null)
			{
				using (RegistryKey vsFoldersKey = vsRootUserKey.OpenSubKey(foldersKey))
				{
					if (vsFoldersKey != null)
					{
						foreach (string folder in GetVisualStudioFolderList(vsFoldersKey))
						{
							// if the user has specified folders then the first one will be used automatically
							yield return folder;
							yield break;
						}
					}
				}
			}

			using (RegistryKey vsFoldersKey = vsRootMachineKey.OpenSubKey(foldersKey))
			{
				if (vsFoldersKey != null)
					foreach (string folder in GetVisualStudioFolderList(vsFoldersKey))
						yield return folder;
			}
		}
		#endregion
	}
}
